<?php
	header('Content-type: charset=utf-8');
	define ('XML_XTL_START_TIME', microtime(true));	
	define('XTEN_DIR', dirname(__FILE__));
	require_once XTEN_DIR.'/lib/config.php';
	require_once XTEN_DIR.'/lib/filesystem.php';
	require_once XTEN_DIR.'/lib/log.php';
	
/**
 * XTEN
 * Xml Templates ENgine
 *
 * @version 0.31
 * @author Dmitriy O. Chechotkin
 * @package WadeCore
 */

/**
 *
 */
class XTEN {

	var $vars;

	private static $cfg;
	private $startTime;
	private $caller;
	private $tpl;
	
	private static $init = true;
	private static $tags;
	private static $handlers;
	var $actions;
	
	// loading config
	function __construct($caller = '') {
		Log::target('xten.log', '__DATE__ __TIME__>  __MESSAGE__');
		if (!self::$cfg) self::$cfg = new Config('xten');
		$this->caller = $caller;	
	}
	// loading handlers
	function init() {
		// 2.0 COMPILER INITIALIZATION
		if (self::$init) {
			Log::put('compiler initialization...');
			self::$handlers = array();
			// 2.0 loaging tags & fields 
			{
				if (self::$cfg->xtl_support) {
					self::$tags = FileSystem::dirFiles(self::$cfg->snpdir, "/([A-Z_0-9]*)\.SNP/", self::$cfg->snpdir.'/tags/$1.SNP', '$1');
					if(self::$cfg->xtl_update && count(self::$tags)) {
						$this->XTLUpdate();
					} elseif(count(self::$tags)) {
						$this->XTLSupport();
					}
				}
			}
			
			// 2.0 loading XPATH handlers
			{
				$hfiles = FileSystem::dirFiles(self::$cfg->snpdir, "/([A-Z]*)\.xml/", '$1');
				foreach($hfiles as $hfile) {
					$hfcontent = file_get_contents(self::$cfg->snpdir.'/'.$hfile.'.xml');
					if(!stristr($hfile, '_snp')) $hfcontent = $this->php2cdata($hfcontent);
					//Log::put('loading handler <'.$hfile.'>');
					try {
						$xml = new SimpleXMLElement($hfcontent);
					} catch (Exception $e) {
						file_put_contents(self::$cfg->snpdir.'/'.$hfile.'.err', $hfcontent);
						Log::warning('Handler <'.$hfile.'> failed to load: '.$e->getMessage().'. Precopmpiled handler code saved to file: '.self::$cfg->snpdir.'/'.$hfile.'.err');
					}
					$path = $xml['xpath'].'';
					self::$handlers[$path][] = $hfcontent;
				}
			}
			
			// 2.0 Loading xcalls
			if (self::$cfg->xcall_enable) {
				$hfiles = FileSystem::dirList(self::$cfg->skin_dir, "/([a-z]*)/", '$1');
				foreach($hfiles as $hfile) {
					if (!file_exists(self::$cfg->skin_dir.'/'.$hfile.'/'.$hfile.'.xml')) continue;
					$hfcontent = file_get_contents(self::$cfg->snpdir.'/xcall.xm_');
					$hfcontent = str_replace('//XCALL_REQUIRED', "//$hfile", $hfcontent);
					//Log::put('loading handler <'.$hfile.'>');
					try {
						$xml = new SimpleXMLElement($hfcontent);
					} catch (Exception $e) {
						file_put_contents(self::$cfg->snpdir.'/'.$hfile.'.err', $hfcontent);
						Log::warning('Handler <'.$hfile.'> failed to load: '.$e->getMessage().'. Precopmpiled handler code saved to file: '.self::$cfg->snpdir.'/'.$hfile.'.err');
					}
					$path = $xml['xpath'].'';
					self::$handlers[$path][] = $hfcontent;
				}			
			}
			self::$init = false;
			$GLOBALS['XCALL_LEVEL'] = 0;
			log::put('loaded '.count(self::$handlers).' handlers, '.count(self::$tags).' tags.');
		}
	}

	/**
	 * отображает шаблон из переменной
	 *
	 * @param unknown_type $code
	 * @param unknown_type $parse
	 */
	function exec($code) {
		$this->tpl = $template;
		$this->startTime = microtime(1);
		$code = $this->Compile($code);
		eval($code);
	}

	/**
	 * отображает шаблон из файла
	 *
	 * @param unknown_type $template
	 * @param unknown_type $parse
	 */
	function display($template, $return = false) {
		$this->startTime = microtime(1);
		
		$this->tpl = $template;
		$this->vars['_GET'] = &$_GET;
		$this->vars['_POST'] = &$_POST;
		$this->vars['_SERVER'] = &$_SERVER;
		$this->vars['_COOKIE'] = &$_COOKIE;
		$this->vars['_ENV'] = &$_ENV;
		$this->vars['_FILES'] = &$_FILES;
		$this->vars['_REQUEST'] = &$_REQUEST;
		$this->vars['_SESSION'] = &$_SESSION;
		$this->vars['NULL'] = false;
		$this->vars['TRUE'] = TRUE;
		$this->vars['.XTEN.TMP.CHECK_BEFORE'] = 'if(!(';
		$this->vars['.XTEN.TMP.CHECK_INSIDE'] = ')) { alert(\'unable to submit form. please, check that all fields are correct\'); event.preventDefault(); event.stopPropagation(); false; return 0;} else {';
		$this->vars['.XTEN.TMP.CHECK_AFTER'] = '}';
		
		$tadr = self::$cfg->skin_dir."/$template/$template.xml";
		$padr = self::$cfg->skin_dir."/$template/$template.php";
		$cadr = self::$cfg->cache_dir."/$template.php";
		if (!file_exists($tadr)) Log::fatal("template '$template' not found on file '$tadr'");
		clearstatcache();
		if ((@filemtime($tadr) > @filemtime($cadr)) || (@filemtime($padr) > @filemtime($cadr)) || self::$cfg->forced_compilation) {
			Log::warning("cache of template '$template' ('$cadr') is out of date. Compiling '$template' with cache saving.");
			$code = $this->Compile($tadr);
			if (file_exists(self::$cfg->skin_dir.'/'.$template.'/'.$template.'.php')) {
				Log::put("Founded init file for template $template");
				$code = file_get_contents(self::$cfg->skin_dir.'/'.$template.'/'.$template.'.php') . $code;
			} else {
				Log::warning("Init file (".
					self::$cfg->skin_dir.'/'.$template.'/'.$template.'.php'.
					") for template $template NOT found.");				
			}
			@unlink($cadr);
			Log::put('compiled template size: '.(strlen($code)/1024).'KB');
			file_put_contents($cadr, $code);
			if (!filesize($cadr)) Log::fatal("Can't write to '$cadr'. Please, set directory rights to 777.");
			Log::put("cache '$cadr' for template '$template' updated (".intval(filesize($cadr))." bytes writed).");
		} else {
			Log::put("Date of cache for template $template looks good. Loading cache.");
			$code = file_get_contents($cadr);
		}
		
		if ($return) return file_get_contents($cadr);
		include($cadr);
	}
	
	public function getResult($template) {
		$this->tpl = $template;
		$code = $this->display($template, true);

		$code = str_replace("'", "\'", $code);
		$code = str_replace('<?=', "'.",$code);
		$code = str_replace('?>', '; $res .= '."'",$code);
		$code = str_replace('<?', "';", $code);
		eval ('$res = '."'$code';");
		return $res;
	}

	private function Compile($fileName) {
		$this->init();
		if (file_exists($fileName)) $text = file_get_contents($fileName);
		else {
			$text = $fileName;
			$fileName = '<native code>';
		}
		// 2.0 XPATH HANDLERS COMPILATION 
		{
			$dom = new DOMDocument();
			$dom->loadXML($text);
			$xpath = new DOMXPath($dom);
			$max = 10;
			$x = 0;
			$repeat = true;
			Log::put("Compilation of file $fileName started");
			while (($x<$max) && $repeat) {
				$repeat = false;
				foreach (self::$handlers as $hxpath => $handlers) {
					$tags = $xpath->query($hxpath);
					foreach ($tags as $tag) {
						Log::put("(loop #$x) xpath triggered: $hxpath (".count($handlers)." handlers)");
						$repeat = true;
						$this->replaceVariablesInAttributes($tag);
						foreach ($handlers as $i=>$handler) {
							$handler = $this->replaceHandlerInjects($tag, $handler);
							$handler = new SimpleXMLElement($handler);
							foreach($handler->children() as $op => $action) {
						 		Log::put('Action <'.$op.'> found in handler #'.$i);
								$actionXML = $this->getChildXML($action);
								$code = @file_get_contents(self::$cfg->snpdir.'/'.$op.'.php');
						 		ob_start(array($this, 'action_handler'));
						 		$GLOBALS['__EVALNAME__'] = $op;
								if ($code) eval('?>'.$code.'<?');
								ob_end_flush();
						 		// TODO: warning 'no such action' 
						 	}
						}
					}
				}
				$x++;
			}
			$ret = $dom->saveHTML();
		} 
		if($x == $max) {
			Log::fatal("Compilation of file '$fileName' failed: too many loops ($x). See log.php for details");
			Log::close();
			die();
		}
		$ret = preg_replace('/#VAR\s\$?([^#]*)#/', '<?=$this->vars[strtoupper(\'\\1\')];?>', $ret);
		Log::put("Compilation of file $fileName completed");
		return $ret; 
	}
	
	private function replaceVariablesInAttributes(DOMElement &$node) {
		foreach($node->attributes as $name=>$attr) {
			$val = preg_replace('/([^\\\\]|^)\$(\w+)/', '\1$this->vars[strtoupper("\2")]', $node->getAttribute($name));
			$node->setAttribute($name, $val);
		}
	}
	
	private function replaceHandlerInjects(DOMElement $tag, $handler) {
		$code = $handler;
		$t = simplexml_import_dom($tag);
		$t = $this->getChildXML($t);
		$code = str_replace('@$DATA', $this->php2cdata($t), $code);
		// double \
		//$code = preg_replace('/([^\\\])(@?)\\$(\\w[\\w\\d]+)/', "\\1\\2\\\\$\\3", $code);
		$code = str_replace('\\', '\\\\', $code);
		$code = preg_replace('/([^\\\])@\\$(\\d+)/', '\\1\'.((strstr($tag->attributes->item(\\2)->value, "$"))?$tag->attributes->item(\\2)->value : "\'".str_replace("\'", "\\\'", $tag->attributes->item(\\2)->value)."\'").\'',
								str_replace("'", '\\\'', $code)
							);

		$code = str_replace('?'.'>', "?'.'>", "'".
					str_replace("\r\n", "'.\r\n'", 
						preg_replace('/([^\\\])@\\$_(\\d+)/', '\\1\'.$tag->attributes->item(\\2)->name.\'', $code."';")
					)
				);
		eval ('$code = '.$code);
		
		// double \
		$code = preg_replace('/([^\\\@]|^)\\$([\\w\\d]+)/', "\\1\\\\$\\2", $code);
		// 
		$code = '$code = '."<<<TEXT\r\n".preg_replace('/([^\\\]|^)@\\$_(\\w[\\w\\d]+)/', "\\1\r\n".
			"TEXT;\r\n".
			"\$code .= ((\$tag->hasAttribute(strtolower('\\2')) ? \r\n".
				"\t((strstr(\$tag->getAttribute(strtolower('\\2')), '$')) ? \r\n".
					"\t\t\$tag->getAttribute(strtolower('\\2')) \r\n".
				"\t:\r\n".
					"\t\"'\".\$tag->getAttribute(strtolower('\\2')).\"'\")\r\n".
			":'@\$\\2'));\r\n".
			"\$code .= <<<TEXT\r\n", $code)."\r\nTEXT;\r\n";
		$code = str_replace('@$', '@\\$', $code);
		eval($code);
		$code = str_replace('@$false', 'false', str_replace('@$true', 'true', $code));
		$code = preg_replace('/([^\\\]|^)@\\$(\\w+)/', '\1$this->vars[strtoupper("\2")]', $code);
		return $code;
	}
	
	private function php2cdata($code) {
		//$code = preg_replace('/(<(\\?(php|=)?.*\\?|!DOCTYPE[^>]+)>)/si', '<![CDATA[ \\1 ]]>', $code);
		$code = str_replace( '<?', '<![CDATA[<?', $code);
		$code = str_replace(array('?>'), '?>]]>', $code);
		return $code;
	}
	
	private function cdata($code) {
		$code = '<![CDATA['.$code.']]>';
		
		return $code;
	}	
	
	private function cdata2php($code) {
		$code = preg_replace('/(<![CDATA[<\?(.*)\?>]]>)/', '<\?\1\?>', $code);
		return $code;
	}
	
	private function getChildXML(SimpleXMLElement $el) {
		$name = $el->getName();
		$code = preg_replace("|</?{$name}[^>]*>|", '', $el->asXML());
		$code = str_replace("<?xml version=\"1.0\"?>", '', $code); 
		$code = str_replace(array('<![CDATA[', ']]>'), '', $code);
		return trim($code);
	}
	
	private function XTLSupport() {
		foreach (self::$tags as $tag => $file) {
			$code=file_get_contents($file);
			$xpath = '//'.strtolower($tag);
			$code = "<handler xpath='{$xpath}'>
	<replace>
		<![CDATA[{$code}]]>
	</replace>
</handler>";
			self::$handlers[$xpath][] = $code;
		}
	}
	
	private function XTLUpdate() {
		foreach (self::$tags as $tag => $file) {
			$code=file_get_contents($file);
			$xpath = '//'.strtolower($tag);
			$code = "<handler xpath='{$xpath}'>
	<replace>
		<![CDATA[{$code}]]>
	</replace>
</handler>";
			unlink('./'.self::$cfg->snpdir.'/'.'tags/'.strtoupper($tag).'.php');
			file_put_contents(self::$cfg->snpdir.'/'.$tag.'_snp.xml', $code);
		}
	}
	
	function svn_revision() {
		$rev = str_replace(array('Rev: ', '$'), '', self::$cfg->svn_revision);
		return $rev; 
	}
	
	function svn_date() {
		$rev = str_replace(array('LastChangedDate: ', '$'), '', self::$cfg->svn_date);
		return $rev; 
	}
	
	function action_handler($data) {
		$data = explode("<br />", $data);
		foreach ($data as $line) {
			if (preg_match('/<b>(Warning|Fatal error|Notice)<\\/b>:  /i', $line)) {
				$info = preg_replace('/<b>(.*)<\/b>:\s(.*)\sin.*line <b>(\d+).*/i', '\1|\2|\3', $line);
				$info = explode('|', $info);
				$str = $info[0].': '.$info[1].' on '.$GLOBALS['__EVALNAME__'].' at line '.$info[2];
				if ($info[0] == 'Notice') Log::put($str);
				if ($info[0] == 'Warning') Log::warning($str);
				if (stristr($info[0], 'error')) Log::fatal($str); 
				$ret .= ''.htmlspecialchars($str).'<br/>';
			}
		}
		return $ret;
	}

}

	
?>